const JS_WS_CLIENT_TYPE = 'js-websocket';
const JS_WS_CLIENT_VERSION = '0.0.1';

const RES_OK = 200;
const RES_FAIL = 500;
const RES_OLD_CLIENT = 501;

var Pomelo = Emitter.extend({

    ctor: function(){
        this._super();

        this.socket = null;
        this.reqId = 0;
        this.reqCallbacks = {};
        
        //Map from request id to route
        this.routeMap = {};

        this.heartbeatInterval = 0;
        this.heartbeatTimeout = 0;
        this.nextHeartbeatTimeout = 0;
        this.gapThreshold = 100; // heartbeat gap threashold

        this.heartbeatId = null;
        this.heartbeatTimeoutId = null;

        this.handshakeCallback = null;

        this.handshakeBuffer = {
            'sys': {
                type: JS_WS_CLIENT_TYPE,
                version: JS_WS_CLIENT_VERSION
            },
            'user': {}
        };

        this.initCallback = null;

        this.lastReqTime = 0;
        this.reqGap = 300;      // 访问间隔，太频繁则不发送访问
    },

    init: function(params,cb) {
        this.initCallback = cb;

        var host = params.host;
        var port = params.port;

        var url = 'ws://' + host;
        if (port) {
            url += ':' + port;
        }

        this.initWebSocket(url);
    },

    initWebSocket: function(url) {
        var self = this;

        var onopen = function(event) {
            cc.log("onopen");
            var obj = Package.encode(Package.TYPE_HANDSHAKE,Protocol.strencode(JSON.stringify(self.handshakeBuffer)));
            self.send(obj);
        };

        var onmessage = function(event) {
            cc.log("onmessage");
            self.processPackage(Package.decode(event.data));
            // new package arrived,update the heartbeat timeout
            if (self.heartbeatTimeout) {
                self.nextHeartbeatTimeout = Date.now() + self.heartbeatTimeout;
            }
        };

        var onerror = function(event) {
            self.emit('io-error',event);
            cc.error('socket error: ',event);
        };

        var onclose = function(event) {
            cc.log("onclose");
            self.emit('close',event);
        };

        this.socket = new WebSocket(url);
        this.socket.binaryType = 'arraybuffer';
        this.socket.onopen = onopen;
        this.socket.onmessage = onmessage;
        this.socket.onerror = onerror;
        this.socket.onclose = onclose;
    },

    isReady:function(){
        return this.socket && this.socket.readyState === WebSocket.OPEN;
    },

    disconnect: function() {
        if (this.isReady()) {
            this.socket.close();
            this.socket = null;
        }

        if (this.heartbeatId) {
            clearTimeout(this.heartbeatId);
            this.heartbeatId = null;
        }
        if (this.heartbeatTimeoutId) {
            clearTimeout(this.heartbeatTimeoutId);
            this.heartbeatTimeoutId = null;
        }

    },

    request: function(route,msg,cb,ignoreGap) {
        if(!this.isReady()){
            cc.error("socket is not ready!");
            return;
        }

        var now = Date.now();
        if(!ignoreGap && this.lastReqTime !==0 && (now-this.lastReqTime<this.reqGap)){
            cc.error("request too frequently!");
            return;
        }

        this.lastReqTime = now;

        if (arguments.length === 2 && typeof msg === 'function') {
            cb = msg;
            msg = {};
        } else {
            msg = msg || {};
        }
        route = route || msg.route;
        if (!route) {
            return;
        }

        this.reqId++;
        this.sendMessage(this.reqId,route,msg);

        this.reqCallbacks[this.reqId] = cb;
        this.routeMap[this.reqId] = route;
    },

    notify: function(route,msg) {
        msg = msg || {},
        this.sendMessage(0,route,msg);
    },

    sendMessage: function(reqId,route,msg) {
        var type = reqId ? Message.TYPE_REQUEST: Message.TYPE_NOTIFY;

        msg = Protocol.strencode(JSON.stringify(msg));

        var compressRoute = 0;
        msg = Message.encode(reqId,type,compressRoute,route,msg);
        var packet = Package.encode(Package.TYPE_DATA,msg);
        this.send(packet);
    },

    send: function(packet) {
        if(!this.isReady()){
            cc.error("socket is not ready!");
            return;
        }
        this.socket.send(packet.buffer);
    },

    heartbeat: function(data) {
        if (!this.heartbeatInterval) {
            // no heartbeat
            return;
        }

        var obj = Package.encode(Package.TYPE_HEARTBEAT);
        if (this.heartbeatTimeoutId) {
            clearTimeout(this.heartbeatTimeoutId);
            this.heartbeatTimeoutId = null;
        }

        if (this.heartbeatId) {
            // already in a heartbeat interval
            return;
        }

        var self = this;

        this.heartbeatId = (
            function() {
                self.heartbeatId = null;
                self.send(obj);
                self.nextHeartbeatTimeout = Date.now() + self.heartbeatTimeout;
                self.heartbeatTimeoutId = setTimeout(self.heartbeatTimeoutCb,self.heartbeatTimeout);
            },self.heartbeatInterval);

    },

    heartbeatTimeoutCb: function() {
        var gap = this.nextHeartbeatTimeout - Date.now();
        if (gap > this.gapThreshold) {
            this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb,gap);
        } else {
            this.emit('heartbeat timeout');
            this.disconnect();
        }
    },

    handshake: function(data) {
        data = JSON.parse(Protocol.strdecode(data));
        if (data.code === RES_OLD_CLIENT) {
            this.emit('error','client version not fullfill');
            return;
        }

        if (data.code !== RES_OK) {
            this.emit('error','handshake fail');
            return;
        }

        this.handshakeInit(data);

        var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
        this.send(obj);
        if (this.initCallback) {
            this.initCallback(this.socket);
            this.initCallback = null;
        }
    },

    onData: function(data) {
        //probuff decode
        var msg = Message.decode(data);

        if (msg.id > 0) {
            msg.route = this.routeMap[msg.id];
            delete this.routeMap[msg.id];
            if (!msg.route) {
                return;
            }
        }

        msg.body = this.deCompose(msg);

        this.processMessage(msg);
    },

    onKick: function(data) {
        this.emit('onKick');
    },

    processPackage: function(msg) {
        switch(msg.type){
            case Package.TYPE_HANDSHAKE:
                this.handshake(msg.body);
                break;
            case Package.TYPE_HEARTBEAT:
                this.heartbeat(msg.body);
                break;
            case Package.TYPE_DATA:
                this.onData(msg.body);
                break;
            case Package.TYPE_KICK:
                this.onKick(msg.body);
                break;
            default :
                cc.error("unknown package type!");
                break;
        }
    },

    processMessage: function(msg) {
        if (!msg.id) {
            // server push message
            this.emit(msg.route,msg.body);
        }

        //if have a id then find the callback function with the request
        var cb = this.reqCallbacks[msg.id];

        delete this.reqCallbacks[msg.id];
        if (typeof cb !== 'function') {
            return;
        }

        cb(msg.body);
        return;
    },

    processMessageBatch: function(msgs) {
        for (var i = 0,l = msgs.length; i < l; i++) {
            this.processMessage(msgs[i]);
        }
    },

    deCompose: function(msg) {
        return JSON.parse(Protocol.strdecode(msg.body));
    },

    handshakeInit: function(data) {
        if (data.sys && data.sys.heartbeat) {
            this.heartbeatInterval = data.sys.heartbeat * 1000;         // heartbeat interval
            this.heartbeatTimeout = this.heartbeatInterval * 2;         // max heartbeat timeout
        } else {
            this.heartbeatInterval = 0;
            this.heartbeatTimeout = 0;
        }

        if (typeof this.handshakeCallback === 'function') {
            this.handshakeCallback(data.user);
        }
    }

});


